/*
 * FileTransporter.cpp
 *
 *  Created on: Apr 8, 2011
 *      Author: sms103
 */

#include "FileTransporter.h"

FileTransporter::FileTransporter(SFTPConnection *conn) {
	dataConn = conn;
	msg = new SFTPMessage(dataConn->getTransportMSS());
	string defaultStatsName;
	if(dataConn->isActive())defaultStatsName = "cli-stats.out";
	else defaultStatsName = "serv-stats.out";
	dataConn->paramParser->setStringParam(&statsFileName,"statsFileName",defaultStatsName);
}


bool FileTransporter::sendFile(string filename){
	struct timeval  tvStart;
	dataConn->clearStats();
	gettimeofday(&tvStart,NULL);
	struct timeval  tvEnd;

	//tracks file length in bytes
	uint64_t fileLength = 0;

	bool retval = true;
	ifstream inFile;
	cout << "Sending file "<<filename<<" to other side of conn.."<<endl;
	inFile.open(filename.c_str(),ifstream::binary | ifstream::in);
	if(inFile.fail()){

		retval = false;
	}

	msg->clearData();

	if(retval)msg->getHeader()->type = MSG_TYPE_TRANSF_INIT;
	else msg->getHeader()->type = MSG_TYPE_FAIL;
	msg->getHeader()->dataLength = 0;
	if(!dataConn->sendMessage(msg))retval = false;

	if(!dataConn->recvMessage(msg))retval = false;

	if(msg->getHeader()->type != MSG_TYPE_TRANSF_INIT){
		retval = false;
	}
	if(!retval){
		cout << "sending file failed."<<endl;
	}
	uint32_t maxDataSize = msg->getMaxMessageSize() - sizeof(struct sftp_message_header);

	msg->clearData();
	//start sending file

	while(retval){
		msg->setType(MSG_TYPE_TRANSF_DATA);
		inFile.read((char *)msg->getData(),maxDataSize);
		msg->getHeader()->dataLength = inFile.gcount();
		fileLength += msg->getHeader()->dataLength;
		if(msg->getHeader()->dataLength >0){
			if(!dataConn->sendMessage(msg)){//if sendmessage fails return fail and break
				retval = false;
				break;
			}
		}
		msg->clearData();
		if(inFile.eof()){
			msg->setType(MSG_TYPE_TRANSF_COMPLETE);
			if(!dataConn->sendMessage(msg))retval=false;
			if(!dataConn->recvMessage(msg))retval=false;
			if(msg->getHeader()->type != MSG_TYPE_TRANSF_COMPLETE){
				retval = false;
			}
			break;
		}
	}
	gettimeofday(&tvEnd,NULL);

	//add the seconds (as microseconds)
	uint64_t transferTime = (tvEnd.tv_sec - tvStart.tv_sec)*1000000;

	//add the microseconds
	if(tvEnd.tv_usec >= tvStart.tv_usec){
		transferTime += tvEnd.tv_usec - tvStart.tv_usec;
	}
	else transferTime -= tvStart.tv_usec - tvEnd.tv_usec;

	//convert to seconds
	double secTransferTime = (double)transferTime/1000000;


	inFile.close();

	//write stats to file if transfer was successful
	if(retval){

	ofstream statsFile;
	statsFile.open(statsFileName.c_str(),ofstream::out | ofstream::app);
	if(statsFile.fail()){
		cout << "Error opening stats output file."<<endl;
	}
	else{
		statsFile << "\n" << dataConn->getDescriptorString();
		statsFile << "File Length, "<<fileLength<<"\n";
		statsFile << "Transfer Time (sec), "<< secTransferTime<<"\n";
		statsFile << "Avg rate (KB/sec), "<< fileLength/secTransferTime/1000<<"\n";
		statsFile << "Total messages sent, "<<dataConn->getNetworkStats()->totalPacketsSent<<"\n";
		statsFile << "Total bytes sent, " << dataConn->getNetworkStats()->totalBytesSent<<"\n";
		statsFile << "Segments with errors, "<<dataConn->getNetworkStats()->errorsCaused<<"\n";
		statsFile << "Segments dropped, "<<dataConn->getNetworkStats()->packetsDropped<<"\n\n";
		statsFile.close();
	}

	}
	return retval;

}


bool FileTransporter::recvFile(string filename){
	bool retval = true;
	ofstream outFile;


	msg->clearData();
	if(!dataConn->recvMessage(msg))return false;

	if(msg->getHeader()->type != MSG_TYPE_TRANSF_INIT){
		retval = false;
	}
	else{
		outFile.open(filename.c_str(),ofstream::binary);
		if(outFile.fail()){
			retval = false;
			msg->setType(MSG_TYPE_FAIL);
		}
	}

	//ack the transf init or fail..
	if(!dataConn->sendMessage(msg))retval=false;


	//receive the file
	while(retval){
		if(!dataConn->recvMessage(msg)){
			//if recv returns false handle the error/timeout, abort transf etc
			retval = false;
			break;
		}
		if(msg->getHeader()->type == MSG_TYPE_TRANSF_COMPLETE){
			msg->clearData();
			dataConn->sendMessage(msg);
			break;
		}
		else{
			outFile.write((char *)msg->getData(),msg->getHeader()->dataLength);
		}
	}

	outFile.close();
	return retval;
}


bool FileTransporter::sendDirectoryContents(string pathname){
	bool retval = true;
	cout<<"sending directory contents of "<<pathname<<endl;
	list<string> files;
	if(getDirContent(pathname,files) != 0){
		//handle error
		retval = false;
	}

	msg->clearData();

	if(retval)msg->getHeader()->type = MSG_TYPE_TRANSF_INIT;
	else msg->getHeader()->type = MSG_TYPE_FAIL;
	msg->getHeader()->dataLength = 0;
	if(!dataConn->sendMessage(msg))retval=false;

	if(!dataConn->recvMessage(msg))retval=false;
	//possibly add timeout detection here....
	if(msg->getHeader()->type != MSG_TYPE_TRANSF_INIT){
		retval = false;
	}

	uint32_t maxDataSize = msg->getMaxMessageSize() - sizeof(struct sftp_message_header);

	msg->clearData();
	char delim = ':';

	while(retval){
		while(msg->getHeader()->dataLength < maxDataSize){
			//if we're out of dir contents or the message is full, break
			if(files.empty() || files.front().size()+1 > maxDataSize - msg->getHeader()->dataLength)break;
			msg->addData((void *)files.front().c_str(),files.front().size());
			msg->addData((void *)&delim,1);
			files.pop_front();
		}

		if(msg->getHeader()->dataLength >0){
			if(!dataConn->sendMessage(msg)){//if sendmessage fails return fail and break
				retval = false;
				break;
			}
		}
		msg->clearData();
		if(files.empty()){
			msg->setType(MSG_TYPE_TRANSF_COMPLETE);
			if(!dataConn->sendMessage(msg))retval=false;
			if(!dataConn->recvMessage(msg))retval=false;
			if(msg->getHeader()->type != MSG_TYPE_TRANSF_COMPLETE){
				retval = false;
			}
			break;
		}

	}
	if(retval){
		cout <<"sent file successfully."<<endl;

	}
	else cout <<"Error sending file."<<endl;
	return retval;
}


//returns a list of files in a specific directory
int FileTransporter::getDirContent(string dir, list<string> &files){
    DIR *dp;
    struct dirent *dirp;
    if((dp  = opendir(dir.c_str())) == NULL) {
        cout << "Error(" << errno << ") opening " << dir << endl;
        return errno;
    }

    while ((dirp = readdir(dp)) != NULL) {
        files.push_back(string(dirp->d_name));
        if(dirp->d_type == DIRENT_TYPE_FILE){
        	files.push_back("f");
        }
        else files.push_back("d");
    }
    closedir(dp);
    return 0;
}

//puts contents into contents object and returns true for success
bool FileTransporter::recvDirectoryContents(string& contents){
	bool retval = true;
	contents.clear();
	msg->clearData();
	if(!dataConn->recvMessage(msg))return false;
	if(msg->getHeader()->type != MSG_TYPE_TRANSF_INIT){
		retval = false;
	}
	//ack the transf init
	if(!dataConn->sendMessage(msg))retval=false;

	//receive the file
	while(retval){
		if(!dataConn->recvMessage(msg)){
			//if recv returns false handle the error/timeout, abort transf etc
			retval = false;
			break;
		}
		if(msg->getHeader()->type == MSG_TYPE_TRANSF_COMPLETE){
			msg->clearData();
			if(!dataConn->sendMessage(msg))retval=false;
			break;
		}
		else{
			contents.append((char *)msg->getData(),msg->getHeader()->dataLength);
		}
	}


	//cout<<"directory contents rcvd : "<<contents<<endl;

	return retval;
}


FileTransporter::~FileTransporter() {
	delete(msg);

}
